#include <stdio.h>
#include <string.h>
#include <mpi.h>
#include <omp.h>
#include "eratostenes.h"

#define TAM_PALAVRA 200

/*Pega a proxima palavra no arquivo até um caracter que não seja letra*/
void get_palavra(FILE *fp, char* buffer){
  int i = 0;
  char c;
  
  c = fgetc(fp);
  while(!isalpha(c) && c != EOF){		
    c = fgetc(fp);
  }
  while(isalpha(c) || c == '-' || c == '\''){
    buffer[i] = c;
    c = fgetc(fp);
    i++;
    if(i > TAM_PALAVRA)
      exit(1);
  }
  buffer[i] = '\0';
}

/* Função que verifica se é um palíndromo */
int verifica_palindromo(char *palavra, int *contador){
  int i, j;
  /* Palavras de só uma letra não são consideradas palíndromos */
  if(strlen(palavra) <= 1){
    return 0;
  }
  /* Verifica palíndromo em palavras com mais de uma letra */
  for(i = 0, j = strlen(palavra)-1; i <=j; i++, j--){
    while(!isalpha(palavra[i])) i++;
    while(!isalpha(palavra[j])) j--;
    if(tolower(palavra[i]) != tolower(palavra[j])){
      return 0;
    }
  }
  /* É palíndromo */
  *contador = *contador + 1;
  return 1;
}


/* Ajusta o offset do arquivo até o próximo caracter que não seja letra */
long ajustaOffset(FILE *fp){
  char c;
  do {
    c = fgetc(fp);
  } while (isalpha(c));
  
  return ftell(fp); 
}


/* Função main */
int main(int argc, char* argv[]){
  /* Texto que é lido do arquivo e caracteres que serão ignorados */
  char buffer[TAM_PALAVRA];
  
  /* Variáveis de contagem */
  int globalPalindromos, partialPalindromos = 0;
  int globalNumberPrimos, partialNumberPrimos = 0;  
  
  /* Variáveis para registrar o tempo de início e término de execução */
  double start, end;
  
  /* Ponteiro para o arquivo */
  FILE *fp;
  
  /* Offset para início e fim de leitura do arquivo */
  long posInit, posEnd, posAux;
      
  /* Variáveis auxiliares */
  int ehPalindromo, ehPrimo;
  
  /* Variável para verificação da inicialização do MPI e das leituras do arquivo (EOF) */
  int ret = 0;
  
  /* Variáveis do MPI */
  int rank, nthreads;
  MPI_Status status;
    
  /* Inicializa o MPI */
  ret = MPI_Init(&argc, &argv);
  if(ret != MPI_SUCCESS){
      printf("Erro na inicialização da execução paralela com o MPI");
      return -1;
  }
  
  /* Funções para pegar o número de threads e o rank do processo */
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &nthreads);
  
  /* Registra o tempo de início */
  start = MPI_Wtime();
  
  
  /*=========================================================================================*/  
  /* Execução paralelizada da criação + inicialização do vetor de primos com o ajuste dos offsets do arquivo a ser lido */
  omp_set_num_threads(2);
  #pragma omp parallel sections 
  {
    #pragma omp section
    {
      /* inicialização de primos */
      initPrimos(&partialNumberPrimos);
    }
    #pragma omp section
    {
      /* Cada processo lerâ de uma posição do arquivo */
      /* Abre o arquivo de teste */
      fp = fopen("wikipedia.txt", "r");
      if(fp < 0){
	printf("Arquivo não encontrado\n");
	exit(-1);
      }
      
      fseek(fp, 0, SEEK_END);
      posAux = ftell(fp);
      
      /* Seta os intervalos de leitura do arquivo */
      posInit = rank * (posAux/nthreads);
      posEnd = (rank+1) * (posAux/nthreads);
      
      /* Prepara o fim do offset */
      fseek(fp, posEnd, SEEK_SET);
      posEnd = ajustaOffset(fp);
      
      /* Nós filhos devem acertar o valor do offset inicial */
      fseek(fp, posInit, SEEK_SET);
      if(rank > 0){
	posInit = ajustaOffset(fp);
      }
    }
  }
  /*=========================================================================================*/  
    
  /*=========================================================================================*/  
  while(1){
    /* Lê uma palavra do arquivo */
    get_palavra(fp, buffer);
        
    /* Se chegou ao fim do trecho do arquivo que lerá, sai do loop */
    if(ftell(fp) >= posEnd){
	break;
    }
    
    /*=========================================================================================*/  
    /* Decomposição especulativa: executa paralelamente a verificação de palíndromos com a soma dos caracteres ASCII (que pode ou não ser usado) */
    #pragma omp sections
    {
      #pragma omp section
	ehPalindromo = verifica_palindromo(buffer, &partialPalindromos);
	
      #pragma omp section
	ehPrimo = verificaPrimo(buffer);
    }
    
    /* Se for palindromo, acrescenta os contadores (no caso, já foi feita a verificação se é primo, apenas adicionando-se o resultado) */
    if(ehPalindromo == 1){
      partialNumberPrimos += ehPrimo;
    }
    /*=========================================================================================*/  
  }
  /*=========================================================================================*/  
    
  /* Fecha o arquivo */
  fclose(fp);
  
  /* Espera todas as threads terminarem */
  MPI_Barrier(MPI_COMM_WORLD);
  
  /* Junta os resultados parciais */
  MPI_Reduce(&partialPalindromos, &globalPalindromos, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(&partialNumberPrimos, &globalNumberPrimos, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
  
  /* Registra o tempo de término */
  end = MPI_Wtime();

  /* Finaliza a parte paralelizada pelo MPI */
  MPI_Finalize();
  
  /* Exibe os resultados */
  if(rank == 0){
    printf("Numero total de palíndromos = %d.\n", globalPalindromos);
    printf("Numero total de palíndromos que são primos = %d.\n", globalNumberPrimos);
    printf("Tempo de execucao = %f segundos\n", end-start);
  }
  
  return 0;
}
